#!/bin/bash
{
	#////////////////////////////////////
	# DietPi Update
	#
	#////////////////////////////////////
	# Created by Daniel Knight / daniel.knight@dietpi.com / dietpi.com
	#
	#////////////////////////////////////
	#
	# Info:
	# - Location: /boot/dietpi/dietpi-update
	# - Updates DietPi from Git or dietpi.com repo
	# - Uses pre-patch_file for patches that need to be done prior to APT calls and DietPi code update
	# - Uses patch_file for incremental patching after APT calls and DietPi code update
	#
	# Usage:
	# - dietpi-update	= Check for DietPi update and in case open interactive menu
	#			  Else, if CONFIG_CHECK_APT_UPDATES is set, check for APT updates and store results to /run/dietpi/.apt_updates to be used by DietPi-Banner
	# - dietpi-update 1	= Check for DietPi update and in case apply noninteractively
	#			  Else, if CONFIG_CHECK_APT_UPDATES=2 is set, check for and apply APT upgrades noninteractively.
	# - dietpi-update 2	= Check for DietPi update only and in case store result to /run/dietpi/.update_available to be used by DietPi-Banner
	#			  If CONFIG_CHECK_APT_UPDATES is set, check for APT updates and store results to /run/dietpi/.apt_updates to be used by DietPi-Banner
	# - dietpi-update -1	= Like "1" but internally reduce subversion by 1 to reapply the last update, e.g. to apply latest dev branch changes
	#////////////////////////////////////

	# Import DietPi-Globals --------------------------------------------------------------
	. /boot/dietpi/func/dietpi-globals
	readonly G_PROGRAM_NAME='DietPi-Update'
	G_CHECK_ROOT_USER
	G_CHECK_ROOTFS_RW
	G_INIT
	# Import DietPi-Globals --------------------------------------------------------------

	# Grab input
	disable_error=1 G_CHECK_VALIDINT "$1" && INPUT=$1 || INPUT=0

	# Prefix G_DIETPI-NOTIFY 3 header text with "Phase" instead of "Mode" when not checking for available updates only
	[[ $INPUT == 2 ]] || G_NOTIFY_3_MODE='Phase'

	#/////////////////////////////////////////////////////////////////////////////////////
	# UPDATE Vars
	#/////////////////////////////////////////////////////////////////////////////////////
	readonly FP_LOG='/var/tmp/dietpi/logs/dietpi-update.log'

	# Git repo to update from
	GITOWNER_TARGET=$(sed -n '/^[[:blank:]]*DEV_GITOWNER=/{s/^[^=]*=//p;q}' /boot/dietpi.txt)
	GITOWNER_TARGET=${GITOWNER_TARGET:-MichaIng}
	GITBRANCH_TARGET=$(sed -n '/^[[:blank:]]*DEV_GITBRANCH=/{s/^[^=]*=//p;q}' /boot/dietpi.txt)
	GITBRANCH_TARGET=${GITBRANCH_TARGET:-master}

	# Remote version variables
	# - Available DietPi version
	G_REMOTE_VERSION_CORE=
	G_REMOTE_VERSION_SUB=
	G_REMOTE_VERSION_RC=
	# - Minimum DietPi version to allow update
	G_MIN_VERSION_CORE=
	G_MIN_VERSION_SUB=
	# - Alternative Git branch to automatically migrate to when version is too low
	G_OLD_VERSION_BRANCH=
	# - Minimum Debian version to allow update
	G_MIN_DEBIAN=
	# - Alternative Git branch to automatically migrate to when Debian version is too low
	G_OLD_DEBIAN_BRANCH=

	# Version info to print to console
	INFO_CURRENT_VERSION=
	INFO_SERVER_VERSION=
	INFO_VERSIONS_UPDATE(){

		INFO_CURRENT_VERSION="Current version : v$G_DIETPI_VERSION_CORE.$G_DIETPI_VERSION_SUB.$G_DIETPI_VERSION_RC"
		INFO_SERVER_VERSION="Latest version  : v$G_REMOTE_VERSION_CORE.$G_REMOTE_VERSION_SUB.$G_REMOTE_VERSION_RC"

		# Show Git repo, when it changed
		[[ $G_GITBRANCH != "$GITBRANCH_TARGET" || $G_GITOWNER != "$GITOWNER_TARGET" ]] || return

		INFO_CURRENT_VERSION+=" (branch: $G_GITOWNER/$G_GITBRANCH)"
		INFO_SERVER_VERSION+=" (branch: $GITOWNER_TARGET/$GITBRANCH_TARGET)"

	}

	Get_Server_Version(){

		local url="https://raw.githubusercontent.com/$GITOWNER_TARGET/DietPi/$GITBRANCH_TARGET/.update/version"

		G_DIETPI-NOTIFY 2 "Getting repository version: $url"
		if curl -sSfL "$url" -o version; then

			# Source file
			. version

			# Check if remote version consists of valid integers
			if disable_error=1 G_CHECK_VALIDINT "$G_REMOTE_VERSION_CORE" &&
				disable_error=1 G_CHECK_VALIDINT "$G_REMOTE_VERSION_SUB" &&
				disable_error=1 G_CHECK_VALIDINT "$G_REMOTE_VERSION_RC"; then

				G_DIETPI-NOTIFY 0 'Got valid repository version'
				return 0

			else

				G_DIETPI-NOTIFY 1 "Got invalid repository version (${G_REMOTE_VERSION_CORE:-NULL}.${G_REMOTE_VERSION_SUB:-NULL}.${G_REMOTE_VERSION_RC:-NULL}). Please check the target Git reposiory and your network connection, then rerun dietpi-update."

			fi

		else

			G_DIETPI-NOTIFY 1 'Unable to get repository version. Please check your network connection, then rerun dietpi-update.'

		fi

		# No valid update server response
		G_DIETPI-NOTIFY 1 'If this error persists, please report at: https://github.com/MichaIng/DietPi/issues'
		return 1

	}

	Apply_1st_Run_Update_Success(){ [[ $G_DIETPI_INSTALL_STAGE == [12] ]] || echo 1 > /boot/dietpi/.install_stage; }

	Check_Git_Migration()
	{
		# Automatically migrate to alternative branch if Debian or DietPi version is too low.
		if (( $G_DISTRO < $G_MIN_DEBIAN ))
		then
			G_DIETPI-NOTIFY 2 "Your Debian version is too low to update to the current branch: $G_DISTRO_NAME ($G_DISTRO)"
			G_DIETPI-NOTIFY 2 "We're switching to an alternative branch: $G_OLD_DEBIAN_BRANCH"
			GITBRANCH_TARGET=$G_OLD_DEBIAN_BRANCH
			Get_Server_Version || return 1
			G_CONFIG_INJECT 'DEV_GITBRANCH=' "DEV_GITBRANCH=$GITBRANCH_TARGET" /boot/dietpi.txt

		elif (( $G_DIETPI_VERSION_CORE < $G_MIN_VERSION_CORE || ( $G_DIETPI_VERSION_CORE == $G_MIN_VERSION_CORE && $G_DIETPI_VERSION_SUB < $G_MIN_VERSION_SUB ) ))
		then
			G_DIETPI-NOTIFY 2 "Your DietPi version is too low to update to the current branch: v$G_DIETPI_VERSION_CORE.$G_DIETPI_VERSION_SUB"
			G_DIETPI-NOTIFY 2 "We're switching to an alternative branch: $G_OLD_VERSION_BRANCH"
			GITBRANCH_TARGET=$G_OLD_VERSION_BRANCH
			Get_Server_Version || return 1
			G_CONFIG_INJECT 'DEV_GITBRANCH=' "DEV_GITBRANCH=$GITBRANCH_TARGET" /boot/dietpi.txt
		fi
	}

	Check_DietPi_Update(){

		# If requested, reduce current subversion by 1 to reapply last update
		if (( $INPUT == -1 )); then

			((G_DIETPI_VERSION_SUB--))
			G_DIETPI_VERSION_RC=0
			G_DIETPI-NOTIFY 2 "Repatch was requested: Subversion reduced to \e[33m\"$G_DIETPI_VERSION_SUB\"\e[90m to reapply the last update"
			INPUT=1

		fi

		local result=1

		# Update available
		if (( $G_DIETPI_VERSION_CORE < $G_REMOTE_VERSION_CORE ||
			( $G_DIETPI_VERSION_CORE == $G_REMOTE_VERSION_CORE && ( $G_DIETPI_VERSION_SUB < $G_REMOTE_VERSION_SUB ||
			( $G_DIETPI_VERSION_SUB == $G_REMOTE_VERSION_SUB && $G_DIETPI_VERSION_RC < $G_REMOTE_VERSION_RC ) ) ) )); then

			result=0

			# Write available update version to flag file
			echo "$G_REMOTE_VERSION_CORE.$G_REMOTE_VERSION_SUB.$G_REMOTE_VERSION_RC" > /run/dietpi/.update_available

			G_DIETPI-NOTIFY 0 'Update available:'

		# No update required
		else

			# Mark 1st run update as completed
			Apply_1st_Run_Update_Success

			# Remove flag file
			[[ -f '/run/dietpi/.update_available' ]] && rm /run/dietpi/.update_available

			G_DIETPI-NOTIFY 0 'No update required, your DietPi installation is already up to date:'

		fi

		INFO_VERSIONS_UPDATE
		G_DIETPI-NOTIFY 2 "$INFO_CURRENT_VERSION"
		G_DIETPI-NOTIFY 2 "$INFO_SERVER_VERSION"

		return $result

	}

	#/////////////////////////////////////////////////////////////////////////////////////
	# Update DietPi
	#/////////////////////////////////////////////////////////////////////////////////////
	Run_DietPi_Update(){

		# RC-only update: Reapply last subversion patches
		if (( $G_DIETPI_VERSION_CORE == $G_REMOTE_VERSION_CORE && $G_DIETPI_VERSION_SUB == $G_REMOTE_VERSION_SUB )); then

			((G_DIETPI_VERSION_SUB--))
			G_DIETPI_VERSION_RC=0
			G_DIETPI-NOTIFY 2 "RC update: Subversion intentionally reduced to \e[33m\"$G_DIETPI_VERSION_SUB\"\e[90m to reapply the last update"

		fi

		G_DIETPI-NOTIFY 3 "$G_PROGRAM_NAME" 'Applying pre-patches'

		# DietPi v6 pre-patches, required for DietPi pre-v6.17 systems, which have DietPi-Update restarted before having these pre-patches applied.
		if (( $G_DIETPI_VERSION_CORE == 6 ))
		then
			G_EXEC_DESC='Downloading pre-patches' G_EXEC curl -sSfLO "https://raw.githubusercontent.com/$GITOWNER_TARGET/DietPi/$GITBRANCH_TARGET/dietpi/pre-patch_file"
			G_EXEC_DESC='Applying execute permission' G_EXEC chmod +x pre-patch_file
			if ! ./pre-patch_file $G_DIETPI_VERSION_SUB
			then
				G_DIETPI-NOTIFY 1 "An error occured during pre-patch $?. Please check the above log or $FP_LOG for errors, and rerun \"dietpi-update\" after the cause has been solved."
				exit 1
			fi

		# DietPi v6 pre-patches internally apply DietPi v7 pre-patches, hence do not apply then separately.
		else
			G_EXEC_DESC='Downloading pre-patches' G_EXEC curl -sSfLO "https://raw.githubusercontent.com/$GITOWNER_TARGET/DietPi/$GITBRANCH_TARGET/.update/pre-patches"
			G_EXEC_DESC='Applying execute permission' G_EXEC chmod +x pre-patches
			if ! ./pre-patches
			then
				G_DIETPI-NOTIFY 1 "An error occured during pre-patching. Please check the above log or $FP_LOG for errors, and rerun \"dietpi-update\" after the cause has been solved."
				exit 1
			fi
		fi

		G_DIETPI-NOTIFY 0 'Successfully applied pre-patches'

		G_DIETPI-NOTIFY 3 "$G_PROGRAM_NAME" 'Upgrading APT packages'
		G_AGUP
		G_AGUG

		G_DIETPI-NOTIFY 3 "$G_PROGRAM_NAME" 'Installing new DietPi code'
		G_EXEC_DESC='Downloading update archive' G_EXEC curl -sSfLO "https://github.com/$GITOWNER_TARGET/DietPi/archive/$GITBRANCH_TARGET.tar.gz"
		G_EXEC_DESC='Unpacking update archive' G_EXEC tar xf "$GITBRANCH_TARGET.tar.gz"
		rm "$GITBRANCH_TARGET.tar.gz"

		# Remove files from Git archive that are not to be installed on client
		rm "DietPi-$GITBRANCH_TARGET/dietpi/"{pre-patch_file,server_version-6}

		G_EXEC_DESC='Installing new DietPi scripts' G_EXEC cp -Rf "DietPi-$GITBRANCH_TARGET/dietpi" /boot/
		G_EXEC_DESC='Installing new DietPi system files' G_EXEC cp -Rf "DietPi-$GITBRANCH_TARGET/rootfs/." /
		G_EXEC_DESC='Setting execute permissions for DietPi scripts' G_EXEC chmod -R +x /boot/dietpi /var/lib/dietpi/services /etc/cron.*/dietpi "DietPi-$GITBRANCH_TARGET/.update/patches"

		# Save version + Git info now for sub scripts to pull from correct branch
		G_GITOWNER=$GITOWNER_TARGET
		G_GITBRANCH=$GITBRANCH_TARGET
		G_VERSIONDB_SAVE

		# Verify/update dietpi.txt entries
		if ! /boot/dietpi/func/dietpi-set_software verify_dietpi.txt; then

			G_DIETPI-NOTIFY 1 "An error occured during dietpi.txt updates. Please check the above log or $FP_LOG for errors, and rerun \"dietpi-update\" after the cause has been solved."
			exit 1

		fi

		# Failsafe: Force sync to disk
		sync

		# Reload systemd units
		systemctl daemon-reload

		G_DIETPI-NOTIFY 3 "$G_PROGRAM_NAME" 'Applying incremental patches'
		INFO_VERSIONS_UPDATE
		G_DIETPI-NOTIFY 2 "$INFO_CURRENT_VERSION"
		G_DIETPI-NOTIFY 2 "$INFO_SERVER_VERSION"

		# DietPi v6 incremental patches
		if (( $G_DIETPI_VERSION_CORE == 6 )) && ! /boot/dietpi/patch_file; then

			G_DIETPI-NOTIFY 1 "An error occured during incremental patching. Please check the above log or $FP_LOG for errors, and rerun \"dietpi-update\" after the cause has been solved."
			exit 1

		fi
		# - Remove patch_file
		rm /boot/dietpi/patch_file

		if ! "DietPi-$G_GITBRANCH/.update/patches"; then

			G_DIETPI-NOTIFY 1 "An error occured during incremental patching. Please check the above log or $FP_LOG for errors, and rerun \"dietpi-update\" after the cause has been solved."
			exit 1

		fi

		# Autoremove possibly obsolete DEB packages and reload systemd units
		G_AGA
		systemctl daemon-reload

		G_DIETPI_VERSION_CORE=$G_REMOTE_VERSION_CORE
		G_DIETPI_VERSION_SUB=$G_REMOTE_VERSION_SUB
		G_DIETPI_VERSION_RC=$G_REMOTE_VERSION_RC
		G_VERSIONDB_SAVE
		G_DIETPI-NOTIFY 0 "Incremental patching to v$G_REMOTE_VERSION_CORE.$G_REMOTE_VERSION_SUB.$G_REMOTE_VERSION_RC completed"

	}

	#/////////////////////////////////////////////////////////////////////////////////////
	# MENUS
	#/////////////////////////////////////////////////////////////////////////////////////
	Get_View_Changelog(){

		[[ -f 'CHANGELOG.txt' ]] || G_EXEC_NOEXIT=1 G_EXEC curl -sSfLO "https://raw.githubusercontent.com/$GITOWNER_TARGET/DietPi/$GITBRANCH_TARGET/CHANGELOG.txt" || return 1
		G_WHIP_VIEWFILE	CHANGELOG.txt

	}

	Menu_Update(){

		while :
		do

			G_WHIP_BUTTON_CANCEL_TEXT='Exit'
			G_WHIP_MENU_ARRAY=(

				'' '●─ Update DietPi '
				'Update' ": Apply update to: v$G_REMOTE_VERSION_CORE.$G_REMOTE_VERSION_SUB.$G_REMOTE_VERSION_RC"
				'' '●─ Additional Options '
				'Changelog' ': View recent changelog and patch notes.'
				'Backup' ': Create a system backup before updating.'

			)

			G_WHIP_DEFAULT_ITEM='Update'
			if G_WHIP_MENU "Update available:\n - $INFO_CURRENT_VERSION\n - $INFO_SERVER_VERSION\n
Please select 'Update' option to apply the update."; then

				if [[ $G_WHIP_RETURNED_VALUE == 'Update' ]]; then

					G_WHIP_SIZE_X_MAX=80
					G_WHIP_YESNO ">----------------------------------Notice----------------------------------<
- A benefit of DietPi is: We use standard Linux (Debian) configurations and commands.
- A potential downside is: We can't possibly accommodate or predict all modification to Linux configurations files by the end user, outside of DietPi programs, during updates.\n
Although we test the updates thoroughly, if you have made any custom changes to Linux configuration files outside of the DietPi programs, an update may trigger a potential issue.
>--------------------------------------------------------------------------<\n
Do you wish to continue and update DietPi to v$G_REMOTE_VERSION_CORE.$G_REMOTE_VERSION_SUB.$G_REMOTE_VERSION_RC?" && return 0

				elif [[ $G_WHIP_RETURNED_VALUE == 'Changelog' ]]; then

					Get_View_Changelog

				elif [[ $G_WHIP_RETURNED_VALUE == 'Backup' ]]; then

					G_PROMPT_BACKUP

				fi

			else

				return 1 # Exit

			fi

		done

	}

	#/////////////////////////////////////////////////////////////////////////////////////
	# Main Loop
	#/////////////////////////////////////////////////////////////////////////////////////
	#----------------------------------------------------------------
	G_DIETPI-NOTIFY 3 "$G_PROGRAM_NAME" 'Checking for available DietPi update'
	#----------------------------------------------------------------
	# Check for DietPi update and in case store result to /run/dietpi/.update_available for use by DietPi-Banner
	if Get_Server_Version && Check_Git_Migration && Check_DietPi_Update; then

		# Exit if check-only input
		(( $INPUT == 2 )) && exit 0

		G_DIETPI-NOTIFY 3 "$G_PROGRAM_NAME" 'Checking for update pre-requirements'

		# Verify userdata location
		G_CHECK_USERDATA

		# Check for sufficient free space
		G_CHECK_FREESPACE / 100 || exit 1

		#----------------------------------------------------------------
		# Noninteractive update or ask user
		if (( $INPUT == 1 )) || Menu_Update; then

			# Disable powersaving on main screen
			setterm -blank 0 -powersave off 2> /dev/null

			# Stop Services
			/boot/dietpi/dietpi-services stop

			# Run_DietPi_Update: https://github.com/MichaIng/DietPi/issues/1877#issuecomment-403866204
			# - Log to file by redirecting to subshell instead of piping, else G_EXEC cannot exit the script via "kill -INT $$": https://github.com/MichaIng/DietPi/issues/3127
			Run_DietPi_Update &> >(tee $FP_LOG); wait $!

			# Mark 1st run update as completed
			Apply_1st_Run_Update_Success

			# Remove .update_available flag file
			rm /run/dietpi/.update_available

			G_DIETPI-NOTIFY 3 "$G_PROGRAM_NAME" 'Completed'
			INFO_VERSIONS_UPDATE
			G_DIETPI-NOTIFY 2 "$INFO_CURRENT_VERSION"
			G_DIETPI-NOTIFY 2 "$INFO_SERVER_VERSION"

			# Update DietPi-Survey
			/boot/dietpi/dietpi-survey 1

			# Failsafe: Force sync to disk
			sync

			# Start services only on finished install state, else dietpi-software will follow immediately
			(( $G_DIETPI_INSTALL_STAGE == 2 )) && /boot/dietpi/dietpi-services restart

		fi
		#----------------------------------------------------------------
		# Desktop run, exit key prompt
		[[ $DISPLAY ]] && read -rp 'Press any key to exit DietPi-Update...'

	# Else, check for and in case apply APT updates based on input mode and dietpi.txt choice
	else
		mode=$(sed -n '/^[[:blank:]]*CONFIG_CHECK_APT_UPDATES=/{s/^[^=]*=//p;q}' /boot/dietpi.txt)
		# Forced update input and mode 2: Apply APT upgrades, if available
		if [[ $INPUT == 1 && $mode == 2 ]]
		then
			G_AGUP -v
			if (( $G_AGUP_COUNT ))
			then
				G_AGUG &> >(tee /var/tmp/dietpi/logs/dietpi-upgrade_apt.log)
				# Assure all services are up since APT upgrades might stop them
				(( $G_DIETPI_INSTALL_STAGE == 2 )) && /boot/dietpi/dietpi-services start
			fi

		# No forced update input but APT updates check not disabled: Check for APT updates and store result to /run/dietpi/.apt_updates to be used by DietPi-Banner
		elif [[ $mode != 0 ]]
		then
			G_AGUP -f
		fi
	fi
	#----------------------------------------------------------------
	exit 0
	#----------------------------------------------------------------
}
